
/*-------------------------------------------------------------------------
 *
 * pl_handler.c		- PL/pgSQL 过程语言的处理程序
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/pl/plpgsql/src/pl_handler.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "plpgsql.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/varlena.h"

static bool fc_plpgsql_extra_checks_check_hook(char **fc_newvalue, void **fc_extra, GucSource fc_source);
static void fc_plpgsql_extra_warnings_assign_hook(const char *fc_newvalue, void *fc_extra);
static void fc_plpgsql_extra_errors_assign_hook(const char *fc_newvalue, void *fc_extra);

PG_MODULE_MAGIC;

/* 自定义 GUC 变量 */
static const struct config_enum_entry variable_conflict_options[] = {
	{"error", PLPGSQL_RESOLVE_ERROR, false},
	{"use_variable", PLPGSQL_RESOLVE_VARIABLE, false},
	{"use_column", PLPGSQL_RESOLVE_COLUMN, false},
	{NULL, 0, false}
};

int			plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;

bool		plpgsql_print_strict_params = false;

bool		plpgsql_check_asserts = true;

char	   *plpgsql_extra_warnings_string = NULL;
char	   *plpgsql_extra_errors_string = NULL;
int			plpgsql_extra_warnings;
int			plpgsql_extra_errors;

/* 插件的钩子 */
PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;


static bool fc_plpgsql_extra_checks_check_hook(char **fc_newvalue, void **fc_extra, GucSource fc_source)
{
	char	   *fc_rawstring;
	List	   *fc_elemlist;
	ListCell   *fc_l;
	int			fc_extrachecks = 0;
	int		   *fc_myextra;

	if (pg_strcasecmp(*fc_newvalue, "all") == 0)
		fc_extrachecks = PLPGSQL_XCHECK_ALL;
	else if (pg_strcasecmp(*fc_newvalue, "none") == 0)
		fc_extrachecks = PLPGSQL_XCHECK_NONE;
	else
	{
		/* 需要可修改的字符串副本 */
		fc_rawstring = pstrdup(*fc_newvalue);

		/* 将字符串解析为标识符列表 */
		if (!SplitIdentifierString(fc_rawstring, ',', &fc_elemlist))
		{
			/* 列表中的语法错误 */
			GUC_check_errdetail("List syntax is invalid.");
			pfree(fc_rawstring);
			list_free(fc_elemlist);
			return false;
		}

		foreach(fc_l, fc_elemlist)
		{
			char	   *fc_tok = (char *) lfirst(fc_l);

			if (pg_strcasecmp(fc_tok, "shadowed_variables") == 0)
				fc_extrachecks |= PLPGSQL_XCHECK_SHADOWVAR;
			else if (pg_strcasecmp(fc_tok, "too_many_rows") == 0)
				fc_extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
			else if (pg_strcasecmp(fc_tok, "strict_multi_assignment") == 0)
				fc_extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
			else if (pg_strcasecmp(fc_tok, "all") == 0 || pg_strcasecmp(fc_tok, "none") == 0)
			{
				GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", fc_tok);
				pfree(fc_rawstring);
				list_free(fc_elemlist);
				return false;
			}
			else
			{
				GUC_check_errdetail("Unrecognized key word: \"%s\".", fc_tok);
				pfree(fc_rawstring);
				list_free(fc_elemlist);
				return false;
			}
		}

		pfree(fc_rawstring);
		list_free(fc_elemlist);
	}

	fc_myextra = (int *) malloc(sizeof(int));
	if (!fc_myextra)
		return false;
	*fc_myextra = fc_extrachecks;
	*fc_extra = (void *) fc_myextra;

	return true;
}

static void fc_plpgsql_extra_warnings_assign_hook(const char *fc_newvalue, void *fc_extra)
{
	plpgsql_extra_warnings = *((int *) fc_extra);
}

static void fc_plpgsql_extra_errors_assign_hook(const char *fc_newvalue, void *fc_extra)
{
	plpgsql_extra_errors = *((int *) fc_extra);
}


/*
 * _PG_init()			- 库加载时初始化
 *
 * 请勿将此设置为静态或更改其名称！
 */
void _PG_init(void)
{
	/* 确保我们只进行一次初始化（现在应该是多余的） */
	static bool fc_inited = false;

	if (fc_inited)
		return;

	pg_bindtextdomain(TEXTDOMAIN);

	DefineCustomEnumVariable("plpgsql.variable_conflict",
							 gettext_noop("Sets handling of conflicts between PL/pgSQL variable names and table column names."),
							 NULL,
							 &plpgsql_variable_conflict,
							 PLPGSQL_RESOLVE_ERROR,
							 variable_conflict_options,
							 PGC_SUSET, 0,
							 NULL, NULL, NULL);

	DefineCustomBoolVariable("plpgsql.print_strict_params",
							 gettext_noop("Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."),
							 NULL,
							 &plpgsql_print_strict_params,
							 false,
							 PGC_USERSET, 0,
							 NULL, NULL, NULL);

	DefineCustomBoolVariable("plpgsql.check_asserts",
							 gettext_noop("Perform checks given in ASSERT statements."),
							 NULL,
							 &plpgsql_check_asserts,
							 true,
							 PGC_USERSET, 0,
							 NULL, NULL, NULL);

	DefineCustomStringVariable("plpgsql.extra_warnings",
							   gettext_noop("List of programming constructs that should produce a warning."),
							   NULL,
							   &plpgsql_extra_warnings_string,
							   "none",
							   PGC_USERSET, GUC_LIST_INPUT,
							   fc_plpgsql_extra_checks_check_hook,
							   fc_plpgsql_extra_warnings_assign_hook,
							   NULL);

	DefineCustomStringVariable("plpgsql.extra_errors",
							   gettext_noop("List of programming constructs that should produce an error."),
							   NULL,
							   &plpgsql_extra_errors_string,
							   "none",
							   PGC_USERSET, GUC_LIST_INPUT,
							   fc_plpgsql_extra_checks_check_hook,
							   fc_plpgsql_extra_errors_assign_hook,
							   NULL);

	MarkGUCPrefixReserved("plpgsql");

	plpgsql_HashTableInit();
	RegisterXactCallback(plpgsql_xact_cb, NULL);
	RegisterSubXactCallback(plpgsql_subxact_cb, NULL);

	/* 与可选的仪器插件设置 rendezvous 点 */
	plpgsql_plugin_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");

	fc_inited = true;
}

/* ----------
 * plpgsql_call_handler
 *
 * PostgreSQL 函数管理器和触发器管理器
 * 调用此函数以执行 PL/pgSQL 程序。
 * ----------
 */
PG_FUNCTION_INFO_V1(plpgsql_call_handler);

Datum plpgsql_call_handler(PG_FUNCTION_ARGS)
{
	bool		fc_nonatomic;
	PLpgSQL_function *fc_func;
	PLpgSQL_execstate *fc_save_cur_estate;
	ResourceOwner fc_procedure_resowner;
	volatile Datum fc_retval = (Datum) 0;
	int			fc_rc;

	fc_nonatomic = fcinfo->context &&
		IsA(fcinfo->context, CallContext) &&
		!castNode(CallContext, fcinfo->context)->atomic;

	/*
	 * 连接到 SPI 管理器
	 */
	if ((fc_rc = SPI_connect_ext(fc_nonatomic ? SPI_OPT_NONATOMIC : 0)) != SPI_OK_CONNECT)
		elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(fc_rc));

	/* 查找或编译函数 */
	fc_func = plpgsql_compile(fcinfo, false);

	/* 必须保存和恢复 cur_estate 的先前值 */
	fc_save_cur_estate = fc_func->cur_estate;

	/* 将函数标记为忙，以便它不会被删除 */
	fc_func->use_count++;

	/*
	 * 如果我们需要一个过程生命周期的 resowner 来执行任何 CALL 或 DO
	 * 语句，现在创建它。由于该 resowner 不与任何
	 * 父级相关联，未能释放它将导致进程生命周期泄漏。
	 * 因此，在这里和 PG_TRY 块之间添加任何代码时要非常谨慎。
	 */
	fc_procedure_resowner =
		(fc_nonatomic && fc_func->requires_procedure_resowner) ?
		ResourceOwnerCreate(NULL, "PL/pgSQL procedure resources") : NULL;

	PG_TRY();
	{
		/*
		 * 确定是作为函数还是触发器调用，并调用适当的
		 * 子处理程序
		 */
		if (CALLED_AS_TRIGGER(fcinfo))
			fc_retval = PointerGetDatum(plpgsql_exec_trigger(fc_func,
														  (TriggerData *) fcinfo->context));
		else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
		{
			plpgsql_exec_event_trigger(fc_func,
									   (EventTriggerData *) fcinfo->context);
			/* 在这种情况下没有返回值 */
		}
		else
			fc_retval = plpgsql_exec_function(fc_func, fcinfo,
										   NULL, NULL,
										   fc_procedure_resowner,
										   !fc_nonatomic);
	}
	PG_FINALLY();
	{
		/* 减少使用计数，恢复 cur_estate */
		fc_func->use_count--;
		fc_func->cur_estate = fc_save_cur_estate;

		/* 确保释放程序 resowner（如果有） */
		if (fc_procedure_resowner)
		{
			ResourceOwnerReleaseAllPlanCacheRefs(fc_procedure_resowner);
			ResourceOwnerDelete(fc_procedure_resowner);
		}
	}
	PG_END_TRY();

	/*
	 * 与 SPI 管理器断开连接
	 */
	if ((fc_rc = SPI_finish()) != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(fc_rc));

	return fc_retval;
}

/* ----------
 * plpgsql_inline_handler
 *
 * 被 PostgreSQL 调用以执行匿名代码块
 * ----------
 */
PG_FUNCTION_INFO_V1(plpgsql_inline_handler);

Datum plpgsql_inline_handler(PG_FUNCTION_ARGS)
{
	LOCAL_FCINFO(fc_fake_fcinfo, 0);
	InlineCodeBlock *fc_codeblock = castNode(InlineCodeBlock, DatumGetPointer(PG_GETARG_DATUM(0)));
	PLpgSQL_function *fc_func;
	FmgrInfo	fc_flinfo;
	EState	   *fc_simple_eval_estate;
	ResourceOwner fc_simple_eval_resowner;
	Datum		fc_retval;
	int			fc_rc;

	/*
	 * 连接到 SPI 管理器
	 */
	if ((fc_rc = SPI_connect_ext(fc_codeblock->atomic ? 0 : SPI_OPT_NONATOMIC)) != SPI_OK_CONNECT)
		elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(fc_rc));

	/* 编译匿名代码块 */
	fc_func = plpgsql_compile_inline(fc_codeblock->source_text);

	/* 只是形式上将函数标记为忙 */
	fc_func->use_count++;

	/*
	 * 设置一个虚假的 fcinfo，信息足够满足
	 * plpgsql_exec_function()。特别注意，这将设置
	 * 而不传递任何参数。
	 */
	MemSet(fc_fake_fcinfo, 0, SizeForFunctionCallInfo(0));
	MemSet(&fc_flinfo, 0, sizeof(fc_flinfo));
	fc_fake_fcinfo->flinfo = &fc_flinfo;
	fc_flinfo.fn_oid = InvalidOid;
	fc_flinfo.fn_mcxt = CurrentMemoryContext;

	/*
	 * 为简单表达式执行创建一个私有 EState 和 resowner。
	 * 注意这些没有与事务级资源绑定；它们必须在 DO 块执行时
	 * 经受住任何 COMMIT/ROLLBACK，因为我们将
	 * 无条件尝试在下面清理它们。（因此，在这里与 PG_TRY
	 * 块之间添加任何可能失败的内容时要小心。）请参阅
	 * shared_simple_eval_estate 的注释。
	 *
	 * 由于此 resowner 不与调用事务绑定，我们还可以
	 * 将其用作任何 CALL 语句的“过程” resowner。
	 * 这有助于减少这里出现故障的机会。
	 */
	fc_simple_eval_estate = CreateExecutorState();
	fc_simple_eval_resowner =
		ResourceOwnerCreate(NULL, "PL/pgSQL DO block simple expressions");

	/* 运行函数 */
	PG_TRY();
	{
		fc_retval = plpgsql_exec_function(fc_func, fc_fake_fcinfo,
									   fc_simple_eval_estate,
									   fc_simple_eval_resowner,
									   fc_simple_eval_resowner,	/* 详见上文 */
									   fc_codeblock->atomic);
	}
	PG_CATCH();
	{
		/*
		 * 我们需要清理否则会由失败的 DO 块累积的长期资源，
		 * 主要是语句的缓存计划（可以通过 plpgsql_free_function_memory 刷新），
		 * 用于简单表达式的执行树，这些在私有 EState 中，以及
		 * 由私有 resowner 持有的缓存计划引用计数。
		 *
		 * 在释放私有 EState 之前，我们必须清理指向它的任何
		 * simple_econtext_stack 条目，我们可以通过调用 subxact 回调来完成。
		 * （如果某个外部控制级别进行子事务回滚，它将再次被调用，但不会造成任何损害。）
		 * 我们稍微作弊一下，知道 plpgsql_subxact_cb 不会
		 * 注意其 parentSubid 参数。
		 */
		plpgsql_subxact_cb(SUBXACT_EVENT_ABORT_SUB,
						   GetCurrentSubTransactionId(),
						   0, NULL);

		/* 清理私有 EState 和 resowner */
		FreeExecutorState(fc_simple_eval_estate);
		ResourceOwnerReleaseAllPlanCacheRefs(fc_simple_eval_resowner);
		ResourceOwnerDelete(fc_simple_eval_resowner);

		/* 函数现在应该没有剩余的使用计数 ... */
		fc_func->use_count--;
		Assert(fc_func->use_count == 0);

		/* ... 所以我们可以释放附属存储 */
		plpgsql_free_function_memory(fc_func);

		/* 并传播错误 */
		PG_RE_THROW();
	}
	PG_END_TRY();

	/* 清理私有 EState 和 resowner */
	FreeExecutorState(fc_simple_eval_estate);
	ResourceOwnerReleaseAllPlanCacheRefs(fc_simple_eval_resowner);
	ResourceOwnerDelete(fc_simple_eval_resowner);

	/* 函数现在应该没有剩余的使用计数 ... */
	fc_func->use_count--;
	Assert(fc_func->use_count == 0);

	/* ... 所以我们可以释放附属存储 */
	plpgsql_free_function_memory(fc_func);

	/*
	 * 与 SPI 管理器断开连接
	 */
	if ((fc_rc = SPI_finish()) != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(fc_rc));

	return fc_retval;
}

/* ----------
 * plpgsql_validator
 *
 * 此函数试图在
 * CREATE FUNCTION 时验证 PL/pgSQL 函数。
 * ----------
 */
PG_FUNCTION_INFO_V1(plpgsql_validator);

Datum plpgsql_validator(PG_FUNCTION_ARGS)
{
	Oid			fc_funcoid = PG_GETARG_OID(0);
	HeapTuple	fc_tuple;
	Form_pg_proc fc_proc;
	char		fc_functyptype;
	int			fc_numargs;
	Oid		   *fc_argtypes;
	char	  **fc_argnames;
	char	   *fc_argmodes;
	bool		fc_is_dml_trigger = false;
	bool		fc_is_event_trigger = false;
	int			fc_i;

	if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, fc_funcoid))
		PG_RETURN_VOID();

	/* 获取新函数的pg_proc条目 */
	fc_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcoid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for function %u", fc_funcoid);
	fc_proc = (Form_pg_proc) GETSTRUCT(fc_tuple);

	fc_functyptype = get_typtype(fc_proc->prorettype);

	/* 不允许伪类型结果 */
	/* 除非是 TRIGGER、EVTTRIGGER、RECORD、VOID 或多态 */
	if (fc_functyptype == TYPTYPE_PSEUDO)
	{
		if (fc_proc->prorettype == TRIGGEROID)
			fc_is_dml_trigger = true;
		else if (fc_proc->prorettype == EVENT_TRIGGEROID)
			fc_is_event_trigger = true;
		else if (fc_proc->prorettype != RECORDOID &&
				 fc_proc->prorettype != VOIDOID &&
				 !IsPolymorphicType(fc_proc->prorettype))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("PL/pgSQL functions cannot return type %s",
							format_type_be(fc_proc->prorettype))));
	}

	/* 不允许在参数中使用伪类型（无论是IN还是OUT） */
	/* 除非是 RECORD 和多态 */
	fc_numargs = get_func_arg_info(fc_tuple,
								&fc_argtypes, &fc_argnames, &fc_argmodes);
	for (fc_i = 0; fc_i < fc_numargs; fc_i++)
	{
		if (get_typtype(fc_argtypes[fc_i]) == TYPTYPE_PSEUDO)
		{
			if (fc_argtypes[fc_i] != RECORDOID &&
				!IsPolymorphicType(fc_argtypes[fc_i]))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("PL/pgSQL functions cannot accept type %s",
								format_type_be(fc_argtypes[fc_i]))));
		}
	}

	/* 如果!check_function_bodies则推迟主体检查 */
	if (check_function_bodies)
	{
		LOCAL_FCINFO(fc_fake_fcinfo, 0);
		FmgrInfo	fc_flinfo;
		int			fc_rc;
		TriggerData fc_trigdata;
		EventTriggerData fc_etrigdata;

		/*
		 * 连接到 SPI 管理器（这在编译时需要吗？）
		 */
		if ((fc_rc = SPI_connect()) != SPI_OK_CONNECT)
			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(fc_rc));

		/*
		 * 设置一个虚假的 fcinfo，提供足够的信息以满足
		 * plpgsql_compile()。
		 */
		MemSet(fc_fake_fcinfo, 0, SizeForFunctionCallInfo(0));
		MemSet(&fc_flinfo, 0, sizeof(fc_flinfo));
		fc_fake_fcinfo->flinfo = &fc_flinfo;
		fc_flinfo.fn_oid = fc_funcoid;
		fc_flinfo.fn_mcxt = CurrentMemoryContext;
		if (fc_is_dml_trigger)
		{
			MemSet(&fc_trigdata, 0, sizeof(fc_trigdata));
			fc_trigdata.type = T_TriggerData;
			fc_fake_fcinfo->context = (Node *) &fc_trigdata;
		}
		else if (fc_is_event_trigger)
		{
			MemSet(&fc_etrigdata, 0, sizeof(fc_etrigdata));
			fc_etrigdata.type = T_EventTriggerData;
			fc_fake_fcinfo->context = (Node *) &fc_etrigdata;
		}

		/* 测试编译函数 */
		plpgsql_compile(fc_fake_fcinfo, true);

		/*
		 * 与 SPI 管理器断开连接
		 */
		if ((fc_rc = SPI_finish()) != SPI_OK_FINISH)
			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(fc_rc));
	}

	ReleaseSysCache(fc_tuple);

	PG_RETURN_VOID();
}
